/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using Lucene.Net.Support;
using IndexInput = Lucene.Net.Store.IndexInput;
using BitVector = Lucene.Net.Util.BitVector;

namespace Lucene.Net.Index
{
    
    internal class SegmentTermDocs : TermDocs
    {
        protected internal SegmentReader parent;
        protected internal IndexInput freqStream;
        protected internal int count;
        protected internal int df;
        protected internal BitVector deletedDocs;
        internal int doc = 0;
        internal int freq;
        
        private readonly int skipInterval;
        private readonly int maxSkipLevels;
        private DefaultSkipListReader skipListReader;
        
        private long freqBasePointer;
        private long proxBasePointer;
        
        private long skipPointer;
        private bool haveSkipped;
        
        protected internal bool currentFieldStoresPayloads;
        protected internal bool currentFieldOmitTermFreqAndPositions;

        private bool isDisposed;
        
        public /*protected internal*/ SegmentTermDocs(SegmentReader parent)
        {
            this.parent = parent;
            this.freqStream = (IndexInput) parent.core.freqStream.Clone();
            lock (parent)
            {
                this.deletedDocs = parent.deletedDocs;
            }
            this.skipInterval = parent.core.GetTermsReader().SkipInterval;
            this.maxSkipLevels = parent.core.GetTermsReader().MaxSkipLevels;
        }
        
        public virtual void  Seek(Term term)
        {
            TermInfo ti = parent.core.GetTermsReader().Get(term);
            Seek(ti, term);
        }
        
        public virtual void  Seek(TermEnum termEnum)
        {
            TermInfo ti;
            Term term;
            
            // use comparison of fieldinfos to verify that termEnum belongs to the same segment as this SegmentTermDocs
            if (termEnum is SegmentTermEnum && ((SegmentTermEnum) termEnum).fieldInfos == parent.core.fieldInfos)
            {
                // optimized case
                var segmentTermEnum = ((SegmentTermEnum) termEnum);
                term = segmentTermEnum.Term;
                ti = segmentTermEnum.TermInfo();
            }
            else
            {
                // punt case
                term = termEnum.Term;
                ti = parent.core.GetTermsReader().Get(term);
            }
            
            Seek(ti, term);
        }
        
        internal virtual void  Seek(TermInfo ti, Term term)
        {
            count = 0;
            FieldInfo fi = parent.core.fieldInfos.FieldInfo(term.Field);
            currentFieldOmitTermFreqAndPositions = (fi != null) && fi.omitTermFreqAndPositions;
            currentFieldStoresPayloads = (fi != null) && fi.storePayloads;
            if (ti == null)
            {
                df = 0;
            }
            else
            {
                df = ti.docFreq;
                doc = 0;
                freqBasePointer = ti.freqPointer;
                proxBasePointer = ti.proxPointer;
                skipPointer = freqBasePointer + ti.skipOffset;
                freqStream.Seek(freqBasePointer);
                haveSkipped = false;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        [Obsolete("Use Dispose() instead")]
        public void Close()
        {
            Dispose();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (isDisposed) return;

            freqStream.Dispose();
            if (skipListReader != null)
                skipListReader.Dispose();

            isDisposed = true;
        }

        public int Doc
        {
            get { return doc; }
        }

        public int Freq
        {
            get { return freq; }
        }

        protected internal virtual void  SkippingDoc()
        {
        }
        
        public virtual bool Next()
        {
            while (true)
            {
                if (count == df)
                    return false;
                int docCode = freqStream.ReadVInt();
                
                if (currentFieldOmitTermFreqAndPositions)
                {
                    doc += docCode;
                    freq = 1;
                }
                else
                {
                    doc += Number.URShift(docCode, 1); // shift off low bit
                    if ((docCode & 1) != 0)
                    // if low bit is set
                        freq = 1;
                    // freq is one
                    else
                        freq = freqStream.ReadVInt(); // else read freq
                }
                
                count++;
                
                if (deletedDocs == null || !deletedDocs.Get(doc))
                    break;
                SkippingDoc();
            }
            return true;
        }
        
        /// <summary>Optimized implementation. </summary>
        public virtual int Read(int[] docs, int[] freqs)
        {
            int length = docs.Length;
            if (currentFieldOmitTermFreqAndPositions)
            {
                return ReadNoTf(docs, freqs, length);
            }
            else
            {
                int i = 0;
                while (i < length && count < df)
                {
                    // manually inlined call to next() for speed
                    int docCode = freqStream.ReadVInt();
                    doc += Number.URShift(docCode, 1); // shift off low bit
                    if ((docCode & 1) != 0)
                    // if low bit is set
                        freq = 1;
                    // freq is one
                    else
                        freq = freqStream.ReadVInt(); // else read freq
                    count++;
                    
                    if (deletedDocs == null || !deletedDocs.Get(doc))
                    {
                        docs[i] = doc;
                        freqs[i] = freq;
                        ++i;
                    }
                }
                return i;
            }
        }
        
        private int ReadNoTf(int[] docs, int[] freqs, int length)
        {
            int i = 0;
            while (i < length && count < df)
            {
                // manually inlined call to next() for speed
                doc += freqStream.ReadVInt();
                count++;
                
                if (deletedDocs == null || !deletedDocs.Get(doc))
                {
                    docs[i] = doc;
                    // Hardware freq to 1 when term freqs were not
                    // stored in the index
                    freqs[i] = 1;
                    ++i;
                }
            }
            return i;
        }
        
        
        /// <summary>Overridden by SegmentTermPositions to skip in prox stream. </summary>
        protected internal virtual void  SkipProx(long proxPointer, int payloadLength)
        {
        }
        
        /// <summary>Optimized implementation. </summary>
        public virtual bool SkipTo(int target)
        {
            if (df >= skipInterval)
            {
                // optimized case
                if (skipListReader == null)
                    skipListReader = new DefaultSkipListReader((IndexInput) freqStream.Clone(), maxSkipLevels, skipInterval); // lazily clone
                
                if (!haveSkipped)
                {
                    // lazily initialize skip stream
                    skipListReader.Init(skipPointer, freqBasePointer, proxBasePointer, df, currentFieldStoresPayloads);
                    haveSkipped = true;
                }
                
                int newCount = skipListReader.SkipTo(target);
                if (newCount > count)
                {
                    freqStream.Seek(skipListReader.GetFreqPointer());
                    SkipProx(skipListReader.GetProxPointer(), skipListReader.GetPayloadLength());
                    
                    doc = skipListReader.GetDoc();
                    count = newCount;
                }
            }
            
            // done skipping, now just scan
            do 
            {
                if (!Next())
                    return false;
            }
            while (target > doc);
            return true;
        }
    }
}